package ns

import (
	"google.golang.org/grpc/codes"
	"net"
	"network/v3/internal/downlink/data"
	"network/v3/internal/downlink/multicast"
	"network/v3/internal/downlink/proprietary"
	"network/v3/internal/storage"
	"network/v3/pkg/logger"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"google.golang.org/grpc"

	"github.com/brocaar/chirpstack-api/go/v3/ns"
	"network/v3/internal/config"
	"network/v3/internal/logging"
)

var errToCode = map[error]codes.Code{
	data.ErrFPortMustNotBeZero:     codes.InvalidArgument,
	data.ErrFPortMustBeZero:        codes.InvalidArgument,
	data.ErrNoLastRXInfoSet:        codes.FailedPrecondition,
	data.ErrInvalidDataRate:        codes.Internal,
	data.ErrMaxPayloadSizeExceeded: codes.InvalidArgument,

	proprietary.ErrInvalidDataRate: codes.Internal,

	multicast.ErrInvalidFCnt: codes.InvalidArgument,

	storage.ErrAlreadyExists:              codes.AlreadyExists,
	storage.ErrDoesNotExist:               codes.NotFound,
	storage.ErrInvalidName:                codes.InvalidArgument,
	storage.ErrInvalidAggregationInterval: codes.InvalidArgument,
	storage.ErrInvalidFPort:               codes.InvalidArgument,
}

func errToRPCError(err error) error {
	cause := errors.Cause(err)
	code, ok := errToCode[cause]
	if !ok {
		code = codes.Unknown
	}
	return grpc.Errorf(code, cause.Error())
}

func Setup(c config.Config) error {
	apiConfig := c.NetworkServer.API

	logger.Log.WithFields(log.Fields{
		"bind": apiConfig.Bind,
	}).Info("api: starting network-server api server")

	opts := serverOptions()

	gs := grpc.NewServer(opts...)
	nsAPI := NewNetworkServerAPI()
	ns.RegisterNetworkServerServiceServer(gs, nsAPI)

	ln, err := net.Listen("tcp", apiConfig.Bind)
	if err != nil {
		return errors.Wrap(err, "start api listener error")
	}
	go gs.Serve(ln)

	return nil
}

func serverOptions() []grpc.ServerOption {
	logrusEntry := log.NewEntry(log.StandardLogger())
	logrusOpts := []grpc_logrus.Option{
		grpc_logrus.WithLevels(grpc_logrus.DefaultCodeToLevel),
	}
	fieldExtractFunc := grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)

	return []grpc.ServerOption{
		grpc_middleware.WithUnaryServerChain(
			grpc_ctxtags.UnaryServerInterceptor(fieldExtractFunc),
			grpc_logrus.UnaryServerInterceptor(logrusEntry, logrusOpts...),
			logging.UnaryServerCtxIDInterceptor,
			grpc_prometheus.UnaryServerInterceptor,
		),
		grpc_middleware.WithStreamServerChain(
			grpc_ctxtags.StreamServerInterceptor(fieldExtractFunc),
			grpc_logrus.StreamServerInterceptor(logrusEntry, logrusOpts...),
			grpc_prometheus.StreamServerInterceptor,
		),
	}
}
